package compiler.mine;

/**
 * Created by szj on 2017/2/2.
 */
public class Lexer {
    public static final int ASCII_COUNT = 128;
    private Token[] tokens = new Token[ASCII_COUNT];
    private String regExp;
    private int currentIndex = 0;
    private Token currentToken = Token.EOS;
    private char currentChar;
    private boolean inDoubleQuotations = false;
    private boolean sawSlash = false;

    public Lexer(String regExp) {
        this.regExp = regExp;
        initTokens();
    }

    public boolean match(Token token){
        return currentToken == token;
    }

    public char getCurrentChar(){
        return currentChar;
    }

    public Token getCurrentToken() {
        return currentToken;
    }

    public Token advance() {
        if (currentIndex >= regExp.length()) {
            currentToken = Token.EOS;
            return currentToken;
        }
        if (regExp.charAt(currentIndex) == '"') {
            currentIndex++;
            inDoubleQuotations = true;
        }

        sawSlash = regExp.charAt(currentIndex) == '\\';

        // \r \b ...
        if (!inDoubleQuotations
                && sawSlash
                && regExp.charAt(currentIndex + 1) != '"') {
            handleTransfer();

            // \"
        } else if (sawSlash && regExp.charAt(currentIndex + 1) == '"') {
            currentChar = '"';
            currentIndex += 2;

            // "..."
        } else {
            currentChar = regExp.charAt(currentIndex);
            currentIndex++;
        }
        currentToken = tokens[currentChar];
        return currentToken;
    }

    private int handleTransfer() {
    	/*当转移符 \ 存在时，它必须与跟在它后面的字符或字符串一起解读
    	 *我们处理的转义字符有以下几种形式
    	 * \b backspace
    	 * \f formfeed
    	 * \n newline
    	 * \r carriage return 回车
    	 * \s space 空格
    	 * \t tab
    	 * \e ASCII ESC ('\033')
    	 * \DDD 3位八进制数
    	 * \xDDD 3位十六进制数
    	 * \^C C是任何字符， 例如^A, ^B 在Ascii 表中都有对应的特殊含义
    	 * ASCII 字符表参见：
    	 * http://baike.baidu.com/pic/%E7%BE%8E%E5%9B%BD%E4%BF%A1%E6%81%AF%E4%BA%A4%E6%8D%A2%E6%A0%87%E5%87%86%E4%BB%A3%E7%A0%81/8950990/0/9213b07eca8065387d4c671896dda144ad348213?fr=lemma&ct=single#aid=0&pic=9213b07eca8065387d4c671896dda144ad348213
    	 */

        int rval = 0;
        String exprToUpper = regExp.toUpperCase();
        currentIndex++; //越过转移符 \
        switch (exprToUpper.charAt(currentIndex)) {
            case '\0' :
                rval = '\\';
                break;
            case 'B':
                rval = '\b';
                break;
            case 'F':
                rval = '\f';
                break;
            case 'N' :
                rval = '\n';
                break;
            case 'R' :
                rval = '\r';
                break;
            case 'S':
                rval = ' ';
                break;
            case 'T':
                rval = '\t';
                break;
            case 'E' :
                rval = '\033';
                break;
            case '^':
                currentIndex++;
    		  /*
    		   * 因此当遇到^后面跟在一个字母时，表示读入的是控制字符
    		   * ^@ 在ASCII 表中的数值为0，^A 为1, 字符@在ASCII 表中数值为80， 字符A在ASCII表中数值为81
    		   * 'A' - '@' 等于1 就对应 ^A 在 ASCII 表中的位置
    		   * 具体可参看注释给出的ASCII 图
    		   * 
    		   */
                rval = (char) (regExp.charAt(currentIndex) - '@');
                break;
            case 'X':
    		/*
    		 * \X 表示后面跟着的三个字符表示八进制或十六进制数
    		 */
                currentIndex++; //越过X
                if (isHexDigit(regExp.charAt(currentIndex))) {
                    rval = hex2Bin(regExp.charAt(currentIndex));
                    currentIndex++;
                }

                if (isHexDigit(regExp.charAt(currentIndex))) {
                    rval <<= 4;
                    rval |= hex2Bin(regExp.charAt(currentIndex));
                    currentIndex++;
                }

                if (isHexDigit(regExp.charAt(currentIndex))) {
                    rval <<= 4;
                    rval |= hex2Bin(regExp.charAt(currentIndex));
                    currentIndex++;
                }
                currentIndex--; //由于在函数底部会对currentIndex++ 所以这里先 --
                break;

            default:
                if (isOctDigit(regExp.charAt(currentIndex)) == false) {
                    rval = regExp.charAt(currentIndex);
                }
                else {
                    currentIndex++;
                    rval = oct2Bin(regExp.charAt(currentIndex));
                    currentIndex++;
                    if (isOctDigit(regExp.charAt(currentIndex))) {
                        rval <<= 3;
                        rval |= oct2Bin(regExp.charAt(currentIndex));
                        currentIndex++;
                    }

                    if (isOctDigit(regExp.charAt(currentIndex))) {
                        rval <<= 3;
                        rval |= oct2Bin(regExp.charAt(currentIndex));
                        currentIndex++;
                    }

                    currentIndex--;//由于在函数底部会对currentIndex++ 所以这里先 --
                }
        }

        currentIndex++;
        return rval;
    }

    private int hex2Bin(char c) {
    	/*
    	 * 将十六进制数对应的字符转换为对应的数值，例如
    	 * A 转换为10， B转换为11
    	 * 字符c 必须满足十六进制字符： 0123456789ABCDEF
    	 */
        return (Character.isDigit(c) ? (c) - '0' : (Character.toUpperCase(c) - 'A' + 10)) & 0xf;
    }

    private int oct2Bin(char c) {
    	/*
    	 * 将字符c 转换为对应的八进制数
    	 * 字符c 必须是合法的八进制字符: 01234567
    	 */
        return ((c) - '0') & 0x7;
    }

    private boolean isHexDigit(char c) {
        return (Character.isDigit(c)|| ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'));
    }

    private boolean isOctDigit(char c) {
        return ('0' <= c && c <= '7');
    }

    private void initTokens() {
        for (int i = 0; i < ASCII_COUNT; i++) {
            tokens[i] = Token.L;
        }
        /*以下是特殊字符*/
        tokens['.'] = Token.ANY;
        tokens['^'] = Token.BEGIN;
        tokens['$'] = Token.END;
        tokens[']'] = Token.RIGHT_BRACKET;
        tokens['['] = Token.LEFT_BRACKET;
        tokens['}'] = Token.RIGHT_BRACE;
        tokens[')'] = Token.RIGHT_PAREN;
        tokens['*'] = Token.ASTERISK;
        tokens['-'] = Token.MINUS;
        tokens['{'] = Token.LEFT_BRACE;
        tokens['('] = Token.LEFT_PAREN;
        tokens['?'] = Token.QUESTION_MARK;
        tokens['|'] = Token.OR;
        tokens['+'] = Token.PLUS;
    }

    public enum Token {
        EOS,//正则表达式结尾
        ANY,//.通配符，代表除\n \r外的126个字符
        BEGIN,//^起始
        END,//$结尾
        LEFT_PAREN,//(
        RIGHT_PAREN,//)
        LEFT_BRACKET,//[
        RIGHT_BRACKET,//]
        LEFT_BRACE,//{
        RIGHT_BRACE,//}
        QUESTION_MARK,//?
        ASTERISK,//*
        PLUS,//+
        MINUS,//-
        L,//字符常量
        OR
    }
}
